/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.admin.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.sida.voice.service.VoiceTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springblade.common.constant.MaintenanceSmsConstant;
import org.springblade.common.constant.TemplateMsgConstant;
import org.springblade.common.constant.VoiceConstant;
import org.springblade.common.enums.FeiShuDocumentEnum;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.base.BaseEntity;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.WebUtil;
import org.springblade.modules.admin.dto.NewWorkPlanSendDTO;
import org.springblade.modules.admin.dto.PlanLogDTO;
import org.springblade.modules.admin.dto.UpdateBitableDataDTO;
import org.springblade.modules.admin.entity.*;
import org.springblade.modules.admin.enums.ApproveStatusEnum;
import org.springblade.modules.admin.enums.MaintainPlanContentEnum;
import org.springblade.modules.admin.enums.SignEnum;
import org.springblade.modules.admin.enums.TaskStatusEnum;
import org.springblade.modules.admin.mapper.MaintainPlanContentUserMapper;
import org.springblade.modules.admin.service.*;
import org.springblade.modules.admin.service.feishu.IFeiShuPartsService;
import org.springblade.modules.admin.utils.AsyncUtils;
import org.springblade.modules.admin.vo.ElevatorSimpleInfoVO;
import org.springblade.modules.admin.vo.ElevatorVO;
import org.springblade.modules.admin.vo.MaintainPlanContentUserVO;
import org.springblade.modules.admin.vo.plan.PlanMaintainUserVO;
import org.springblade.modules.admin.vo.plan.PlanUserSignDetailVo;
import org.springblade.modules.admin.vo.plan.SignStatusInfoVo;
import org.springblade.modules.resource.utils.SmsUtil;
import org.springblade.modules.system.entity.User;
import org.springblade.modules.system.service.IUserService;
import org.springblade.modules.system.vo.SimpleUserVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 维保任务关联人员中间表 服务实现类
 *
 * @author BladeX
 * @since 2021-05-10
 */
@Service
@Slf4j
public class MaintainPlanContentUserServiceImpl extends BaseServiceImpl<MaintainPlanContentUserMapper, MaintainPlanContentUser> implements IMaintainPlanContentUserService {

	private static final String SMS_CODE = "maintain";

	/**
	 * 签到签退状态1:正常,其它异常
	 */
	private final int SING_STATUS = 2;

	@Autowired
	@Lazy
	private MaintainMalfunctionLogService malfunctionLogService;

	@Autowired
	@Lazy
	private IMaintainPlanContentService maintainPlanContentService;

	@Autowired
	@Lazy
	private WeChatDialogueService weChatDialogueService;

	@Autowired
	@Lazy
	private IUserService userService;

	@Autowired
	@Lazy
	private IMaintainPlanContentExtService maintainPlanContentExtService;

	@Autowired
	@Lazy
	private MaintainPartsWorkService maintainPartsWorkService;

	@Autowired
	@Lazy
	private ISparepartPricesService sparepartPricesService;

	@Autowired
	@Lazy
	private ElevatorService elevatorService;

	@Autowired
	@Lazy
	private IMaintainSignService maintainSignService;

	@Autowired
	private AsyncUtils asyncUtils;

	@Autowired
	private Environment environment;

	@Autowired
	private IFeiShuPartsService feiShuPartsService;

	@Override
	public IPage<MaintainPlanContentUserVO> selectMaintainPlanContentUserPage(IPage<MaintainPlanContentUserVO> page, MaintainPlanContentUserVO maintainPlanContentUser) {
		return page.setRecords(baseMapper.selectMaintainPlanContentUserPage(page, maintainPlanContentUser));
	}

	@Override
	public List<String> selectLastFinishedPlanUserPhones(Long planId) {
		return baseMapper.selectLastFinishedPlanUserPhones(planId);
	}

	@Override
	public List<PlanMaintainUserVO> selectWorkUsers(Long planId) {
		return baseMapper.selectWorkUsers(planId);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean addBatch(List<MaintainPlanContentUser> list) {

		//剔除已经发过短信的人员
		List<MaintainPlanContentUser> sendList = new ArrayList<>(16);
		for (MaintainPlanContentUser maintainPlanContentUser : list) {
			List<MaintainPlanContentUser> searchList = this.list(Wrappers.<MaintainPlanContentUser>lambdaQuery().eq(MaintainPlanContentUser::getUserId, maintainPlanContentUser.getUserId()).eq(MaintainPlanContentUser::getPlanId, maintainPlanContentUser.getPlanId()));
			if (searchList.size() == 0) {
				sendList.add(maintainPlanContentUser);
			}
		}
		this.saveBatch(sendList);
		List<Long> planIds = list.stream().map(MaintainPlanContentUser::getPlanId).distinct().collect(Collectors.toList());
		List<MaintainPlanContent> maintainPlanContentList = maintainPlanContentService.listByIds(planIds);
		Map<Long, List<MaintainPlanContent>> listMap = maintainPlanContentList.stream().collect(Collectors.groupingBy(MaintainPlanContent::getId));

		List<User> users = userService.listByIds(list.stream().map(MaintainPlanContentUser::getUserId).collect(Collectors.toList()));
		List<String> userNames = users.stream().map(User::getRealName).collect(Collectors.toList());
		for (Long planId : planIds) {
			List<MaintainMalfunctionLog> logs = malfunctionLogService.list(Wrappers.<MaintainMalfunctionLog>lambdaQuery().eq(MaintainMalfunctionLog::getPlanId, planId).eq(MaintainMalfunctionLog::getPlanStatus, TaskStatusEnum.PARTS_TO_START.getStatus()));
			if (logs.size() == 0) {
				//插入下一个节点的状态
				MaintainMalfunctionLog log = new MaintainMalfunctionLog();
				log.setPlanId(planId);
				log.setPlanStatus(TaskStatusEnum.PARTS_TO_START.getStatus());
				log.setTypeName(TaskStatusEnum.PARTS_TO_START.getName());
				log.setExecutorId(AuthUtil.getUserId());
				log.setUserNames(String.join(",", userNames));
				log.setExecutor(AuthUtil.getNickName());
				malfunctionLogService.save(log);

				// 飞书-修改配件状态-待更换
//				if (Func.equalsSafe("592800", AuthUtil.getTenantId())) {
//					FeiShuDocumentEnum documentEnum = null;
//					if (environment.acceptsProfiles("prod")) {
//						documentEnum = FeiShuDocumentEnum.PART_APPLY_RECORD;
//					} else {
//						documentEnum = FeiShuDocumentEnum.PART_APPLY_RECORD_COPY2;
//					}
//					UpdateBitableDataDTO bitableData = new UpdateBitableDataDTO();
//					bitableData.setAppToken(documentEnum.getAppToken());
//					bitableData.setTableId(documentEnum.getTableId());
//					bitableData.setPlanIdField("配件申请编号");
//					bitableData.setPlanId(planId);
//					Map<String, Object> value = new HashMap<>(2);
//					value.put("结果", TaskStatusEnum.PARTS_TO_START.getName() + "-操作成功");
//					bitableData.setValue(value);
//					feiShuPartsService.updateBitableByPlanId(bitableData);
//				}
			} else {
				MaintainPlanContent maintainPlanContent = listMap.get(planId).get(0);
				MaintainMalfunctionLog log = new MaintainMalfunctionLog();
				log.setPlanId(planId);
				log.setPlanStatus(maintainPlanContent.getStatus());
				log.setTypeName(TaskStatusEnum.getTaskName(MaintainPlanContentEnum.MAINTAIN_PARTS.getId(), maintainPlanContent.getStatus()));
				log.setUserNames(String.join(",", userNames));
				log.setExecutorId(AuthUtil.getUserId());
				log.setExecutor(AuthUtil.getNickName());
				PlanLogDTO planLogDTO = new PlanLogDTO();
				//表示转派任务方便前端判断
				int type = 1;
				planLogDTO.setType(type);
				planLogDTO.setDesc("任务转派");
				log.setExt(JSON.toJSONString(planLogDTO));
				malfunctionLogService.save(log);
			}
		}
		final List<SparepartPrices> pricesList = sparepartPricesService.list(Wrappers.<SparepartPrices>lambdaQuery().in(SparepartPrices::getPlanId, planIds));
		pricesList.forEach(e -> e.setApportionTime(new Date()));
		sparepartPricesService.updateBatchById(pricesList);


		for (MaintainPlanContent maintainPlanContent : maintainPlanContentList) {
			if (maintainPlanContent.getStatus() < TaskStatusEnum.PARTS_TO_START.getStatus()) {
				maintainPlanContent.setStatus(TaskStatusEnum.PARTS_TO_START.getStatus());
			}
			maintainPlanContent.setPlanDate(new Date());
		}
		maintainPlanContentService.updateBatchById(maintainPlanContentList);

		//添加工单的扩展表
		final List<MaintainPlanContentExt> contentExtList = maintainPlanContentExtService.list(Wrappers.<MaintainPlanContentExt>lambdaQuery().in(MaintainPlanContentExt::getPlanId, planIds));
		final List<Long> list1 = contentExtList.stream().map(MaintainPlanContentExt::getPlanId).collect(Collectors.toList());
		List<MaintainPlanContentExt> saveList = new ArrayList<>(16);
		for (Long planId : planIds) {
			if (!list1.contains(planId)) {
				MaintainPlanContentExt maintainPlanContentExt = new MaintainPlanContentExt();
				maintainPlanContentExt.setPlanId(planId);
				maintainPlanContentExt.setWorkMinute(0);
				maintainPlanContentExt.setOverdueDay(0);
				saveList.add(maintainPlanContentExt);
			}
		}
		if (saveList.size() > 0) {
			maintainPlanContentExtService.saveBatch(saveList);
		}


		// 给维修人发送消息
		for (MaintainPlanContentUser maintainPlanContentUser : sendList) {
			SimpleUserVO assignUserInfo = userService.getSimpleUserInfo(maintainPlanContentUser.getUserId());
			MaintainPlanContent maintainPlanContent = maintainPlanContentService.getById(maintainPlanContentUser.getPlanId());
			ElevatorSimpleInfoVO elevatorSimpleInfo = maintainPlanContentService.getElevatorSimpleInfo(maintainPlanContent.getId());
			if (Func.isNotEmpty(assignUserInfo)) {

				if (Func.isNotEmpty(assignUserInfo.getOpenid())) {
					final User user = userService.getById(maintainPlanContent.getCreateUser());
//					final MaintainPartsWork maintainPartsWork = maintainPartsWorkService.getOne(Wrappers.<MaintainPartsWork>lambdaQuery().eq(MaintainPartsWork::getPlanId, maintainPlanContent.getId()));
					log.info("===========给维修发送模板消息,维修人:[{}]===========", assignUserInfo.getUserName());
					// 跳转链接
					String skipUrl = String.format("subpages/workOrders/partOrder?elevatorId=%s&planId=%s", maintainPlanContent.getElevatorId(), maintainPlanContent.getId());
					Map<String, String> templateDataMap = new HashMap<>(8);
					templateDataMap.put("first", "您好，您有新的配件任务!");
					templateDataMap.put("keyword1", user.getRealName());
					templateDataMap.put("keyword2", user.getPhone());
					templateDataMap.put("keyword3", DateUtil.format(new Date(), DatePattern.NORM_DATETIME_MINUTE_PATTERN));
					templateDataMap.put("keyword4", "配件更换");
					templateDataMap.put("remark", "请及时登录梯无忧小程序执行配件任务!");
					templateDataMap.put("params", skipUrl);
					weChatDialogueService.sendMsg(assignUserInfo.getOpenid(), TemplateMsgConstant.T_REPAIR_TASK_ID, templateDataMap);
				}

				log.info("===========给维修人发送手机短信,维修人:[{}], 手机号码:[{}]===========", assignUserInfo.getUserName(), assignUserInfo.getPhone());
				Map<String, String> smsParam = new HashMap<>(2);
				smsParam.put("auditStatus", "待开始");
				SmsUtil.sendMessage(MaintenanceSmsConstant.MAINTENANCE_PEOPLE, BladeConstant.ADMIN_TENANT_ID, smsParam, assignUserInfo.getPhone());


				Map<String, String> phoneMap = new HashMap<>(2);
				phoneMap.put("place", elevatorSimpleInfo.getBuildingName() + elevatorSimpleInfo.getElevatorAddress());
				phoneMap.put("location", "梯无忧维保小程序");
				VoiceTemplate voiceTemplate = new VoiceTemplate();
				voiceTemplate.setTemplateCode(VoiceConstant.PARTS_MESSAGE);
				voiceTemplate.setTemplateParam(phoneMap);
				voiceTemplate.setPhoneNumber(assignUserInfo.getPhone());
				asyncUtils.sendPhone(voiceTemplate);
			}
		}

		return true;
	}

	@Override
	public void updatePlanUserSignInfo(Long planId, Integer signType, Long userId, Integer signStatus) {

		List<MaintainPlanContentUser> list = this.list(Wrappers.<MaintainPlanContentUser>lambdaQuery().eq(MaintainPlanContentUser::getPlanId, planId));
		for (MaintainPlanContentUser maintainPlanContentUser : list) {
			String realName = userService.getById(maintainPlanContentUser.getUserId()).getRealName();
			String signStatusInfo = Convert.toStr(maintainPlanContentUser.getSignStatusInfo(), "");
			if (StrUtil.isEmpty(signStatusInfo)) {
				SignStatusInfoVo signStatusInfoVo = new SignStatusInfoVo();
				signStatusInfoVo.setUserName(realName);
				if (signType == SignEnum.SIGN.getId()) {
					signStatusInfoVo.setSignStatus(signStatus);
					signStatusInfoVo.setSignOutStatus(signStatus);
				}
				signStatusInfo = JSON.toJSONString(signStatusInfoVo);
			} else {
				SignStatusInfoVo signStatusInfoVo = JSON.parseObject(signStatusInfo, SignStatusInfoVo.class);
				if (signType == SignEnum.SIGN.getId()) {
					if (signStatus == SING_STATUS) {
						signStatusInfoVo.setSignStatus(signStatus);
					}
				} else if (signType == SignEnum.SIGN_OUT.getId()) {
					if (signStatus == SING_STATUS) {
						signStatusInfoVo.setSignOutStatus(signStatus);
					}
				}
				signStatusInfo = JSON.toJSONString(signStatusInfoVo);
			}
			maintainPlanContentUser.setSignStatusInfo(signStatusInfo);


			PlanUserSignDetailVo planUserSignDetailVo = new PlanUserSignDetailVo();

			planUserSignDetailVo.setPlanId(planId);
			if (maintainPlanContentUser.getUserId().equals(userId)) {
				planUserSignDetailVo.setIsPlanStatus(signType == SignEnum.SIGN.getId() ? true : false);
				planUserSignDetailVo.setIsTakePartIn(true);
				planUserSignDetailVo.setUserName(realName);
				maintainPlanContentUser.setSignInfo(JSON.toJSONString(planUserSignDetailVo));
			} else {
				String signInfo = maintainPlanContentUser.getSignInfo();
				if (StrUtil.isEmpty(signInfo)) {
					planUserSignDetailVo.setIsPlanStatus(false);
					planUserSignDetailVo.setIsTakePartIn(false);
					planUserSignDetailVo.setUserName(realName);
					maintainPlanContentUser.setSignInfo(JSON.toJSONString(planUserSignDetailVo));
				}
			}

		}
		this.updateBatchById(list);
	}

	@Override
	public List<MaintainPlanContentUser> listByPlanId(List<Long> planIds) {
		return baseMapper.listByPlanId(planIds);
	}

	@Override
	public List<MaintainPlanUserDayCount> listInPlanDate() {
		return baseMapper.listInPlanDate();
	}

	@Override
	public List<MaintainPlanUserDayCount> listNotInPlanDate() {
		return baseMapper.listNotInPlanDate();
	}

	@Override
	public List<MaintainPlanContentUser> listByWxPlanId(List<Long> planIds) {
		return baseMapper.listByWxPlanId(planIds);
	}

	@Override
	public List<MaintainPlanContent> selectPlanContentByUser(Long userId) {
		return baseMapper.selectPlanContentByUser(userId);
	}

	/**
	 * 事务工单批量指派/转派
	 *
	 * @param newWorkPlanSendDTO 工单id、指派用户id 集合
	 * @return 是否成功
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean send(NewWorkPlanSendDTO newWorkPlanSendDTO) {
		List<Long> planIds = newWorkPlanSendDTO.getPlanIds();
		List<Long> userIds = newWorkPlanSendDTO.getUserIds();
		Date planDate = newWorkPlanSendDTO.getPlanDate();
		// 即将发送消息/入库的列表
		List<MaintainPlanContentUser> sendList = new ArrayList<>();

		// 测试数据

		// 0. 日期为立即执行的情况:空数据处理
		if (Func.isNull(planDate)) {
			SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
			String todayString = dateFormat.format(new Date());
			Date today = null;
			try {
				today = dateFormat.parse(todayString);
			} catch (Exception e) {
				e.printStackTrace();
			}
			planDate = today;
		}

		/*
		 * 1. 遍历planIds列表, 嵌套遍历userIds列表, 时间复杂度O(n^2)
		 * 遍历工单列表, 查看状态:
		 * 待分配 -> 插入操作,分配, 给所有人发消息, 更新工单状态：待分配 -> 待开始, 更新任务追踪状态;
		 * 待开始 -> 可增删改人员, 逻辑删表数据、给新增的人发消息,更新任务追踪状态;
		 * 进行中 -> 只增不删, 给新增的人发消息,更新任务追踪状态;
		 */
		for (Long planId : planIds) {
			MaintainPlanContent currentPlan = maintainPlanContentService.getById(planId);
			currentPlan.setPlanDate(planDate);
			// 待开始
			if (currentPlan.getStatus().equals(TaskStatusEnum.SW_TO_START.getStatus())) {
				transferNotStarted(planId, planDate, userIds, sendList, currentPlan);
				sendList = new ArrayList<>();
			}
			// 进行中
			else if (currentPlan.getStatus().equals(TaskStatusEnum.SW_UNDER_WAY.getStatus())) {
				transferStarted(planId, planDate, userIds, sendList, currentPlan);
				sendList = new ArrayList<>();
			}
			// 待分配, 由于分配状态会更改, 更改后会命中后续状态, 放到循环尾部
			else if (currentPlan.getStatus().equals(TaskStatusEnum.SW_ALLOCATION.getStatus())) {
				assign(planId, planDate, userIds, sendList, currentPlan);
				sendList = new ArrayList<>();
			}
		}
		// 2023-11-18 需求变更，经协商，为了解决批量导致的信息发送过多问题，改为按人、工单只发1次
		// 发送消息，使用旧模板先测试单条工单 信息
		for (Long userId : userIds) {
			MaintainPlanContentUser maintainPlanContentUser = new MaintainPlanContentUser();
			User user = userService.getById(userId);
			maintainPlanContentUser.setUserName(user.getRealName());
			maintainPlanContentUser.setUserId(userId);
			maintainPlanContentUser.setPlanDate(planDate);
			sendList.add(maintainPlanContentUser);
		}
		sendMessage(sendList, planIds);

		return Boolean.TRUE;
	}



	/**
	 * 事务工单-指派
	 *
	 * @param planId      当前工单 id
	 * @param planDate    工单日期
	 * @param userIds     批量用户列表
	 * @param sendList    发送消息列表
	 * @param currentPlan 当前工单
	 */
	private void assign(Long planId,
						Date planDate,
						List<Long> userIds,
						List<MaintainPlanContentUser> sendList,
						MaintainPlanContent currentPlan) {
		// 已指派去重
		List<Long> existList = this.list(new LambdaQueryWrapper<MaintainPlanContentUser>()
			.eq(MaintainPlanContentUser::getPlanId, planId)
			.eq(BaseEntity::getIsDeleted, 0)
		).stream().map(MaintainPlanContentUser::getUserId).collect(Collectors.toList());

		if (Func.isNotEmpty(existList)) {
			List<Long> filtered = userIds.stream().filter(f -> !existList.contains(f)).collect(Collectors.toList());
			userIds = filtered;
		}
		if (Func.isNotEmpty(userIds)) {
			// 指派
			for (Long userId : userIds) {
				MaintainPlanContentUser maintainPlanContentUser = sendElement(userId, planId, planDate);
				sendList.add(maintainPlanContentUser);
			}
			// 入库
			this.saveBatch(sendList);
			// 更新当前工单状态 待分配 -> 已指派
			currentPlan.setStatus(TaskStatusEnum.SW_TO_START.getStatus());
			maintainPlanContentService.updateById(currentPlan);
			// 更新任务追踪状态, 待分配 -> 分配
			updateWorkMalfunctionLog(planId, sendList, TaskStatusEnum.SW_ALLOCATION.getStatus());
		}
	}

	/**
	 * 事务工单-未开始-转派
	 *
	 * @param planId   当前工单id
	 * @param planDate 计划日期
	 * @param userIds  用户id集合
	 * @param sendList 发送消息日期
	 */
	private void transferNotStarted(Long planId,
									Date planDate,
									List<Long> userIds,
									List<MaintainPlanContentUser> sendList,
									MaintainPlanContent currentPlan) {

		Map<String, List<Long>> diff = getDiff(planId, userIds);
		List<Long> preDeleteList = diff.get("preDeleteList");
		List<Long> preAddList = diff.get("preAddList");

		// 执行删除
		if (Func.isNotEmpty(preDeleteList)) {
			List<MaintainPlanContentUser> deletedTargets = new ArrayList<>();
			for (Long preDeleteUserId : preDeleteList) {
				List<MaintainPlanContentUser> tempDelList = this.list(new LambdaQueryWrapper<MaintainPlanContentUser>()
					.eq(MaintainPlanContentUser::getPlanId, planId)
					.eq(MaintainPlanContentUser::getUserId, preDeleteUserId)
					.eq(BaseEntity::getIsDeleted, 0)
				);
				deletedTargets.addAll(tempDelList);
			}
			if (Func.isNotEmpty(deletedTargets)) {
				this.removeByIds(deletedTargets.stream().map(BaseEntity::getId).collect(Collectors.toList()));
			}

		}

		// 执行新增
		if (Func.isNotEmpty(preAddList)) {
			for (Long userId : preAddList) {
				MaintainPlanContentUser maintainPlanContentUser = sendElement(userId, planId, planDate);
				sendList.add(maintainPlanContentUser);
			}
		}
		// 入库
		this.saveBatch(sendList);
		// 更新任务追踪状态
		updateWorkMalfunctionLog(planId, sendList, currentPlan.getStatus());
	}

	/**
	 * 事务工单-已开始-转派
	 *
	 * @param planId   当前工单id
	 * @param planDate 计划时间
	 * @param userIds  用户id
	 * @param sendList 发送消息列表
	 */
	private void transferStarted(Long planId,
								 Date planDate,
								 List<Long> userIds,
								 List<MaintainPlanContentUser> sendList,
								 MaintainPlanContent currentPlan) {
		Map<String, List<Long>> diff = getDiff(planId, userIds);
		List<Long> preDeleteList = diff.get("preDeleteList");
		List<Long> preAddList = diff.get("preAddList");

		// 根据已选删除列表判断是否存在签到信息，存在：抛异常; 不存在，删除
		if (Func.isNotEmpty(preDeleteList)) {
			preDeleteList.forEach(f -> {
				int isSign = maintainSignService.list(new LambdaQueryWrapper<MaintainSign>()
					.eq(MaintainSign::getPlanId, planId)
					.eq(MaintainSign::getUserId, f)
					.eq(BaseEntity::getIsDeleted, 0)
				).size();
				if (isSign > 0) {
					throw new ServiceException("业务异常！无法删除已签到的派遣人，请重新选择");
				}
			});
			for (int i = 0; i < preDeleteList.size(); i++) {
				MaintainPlanContentUser removeTarget = this.getOne(new LambdaQueryWrapper<MaintainPlanContentUser>()
					.eq(MaintainPlanContentUser::getUserId, preDeleteList.get(i))
					.eq(MaintainPlanContentUser::getPlanId, planId)
					.eq(BaseEntity::getIsDeleted, 0)
				);
				this.removeById(removeTarget.getId());
			}
		}
		// 根据当前 工单id 判断是否已有记录, 是：不执行； 否：指派
		for (Long userId : userIds) {
			int existSize = this.list(new LambdaQueryWrapper<MaintainPlanContentUser>()
				.eq(MaintainPlanContentUser::getPlanId, planId)
				.eq(MaintainPlanContentUser::getUserId, userId)
				.eq(BaseEntity::getIsDeleted, 0)
			).size();
			if (existSize > 0) {
				continue;
			}
			MaintainPlanContentUser maintainPlanContentUser = sendElement(userId, planId, planDate);
			sendList.add(maintainPlanContentUser);
		}
		// 入库
		this.saveBatch(sendList);
		// 更新任务追踪状态
		updateWorkMalfunctionLog(planId, sendList, currentPlan.getStatus());
	}

	/**
	 * 事务工单 指派/转派 发送 模板消息 和 短信 的方法
	 */
	private void sendMessage(List<MaintainPlanContentUser> sendList, List<Long> planIds) {
		String userName = AuthUtil.getUserName();
		// 根据工单 批量组装building、电梯信息
		String thing18 = "";
		for (Long planId : planIds) {
			MaintainPlanContent currentPlan = maintainPlanContentService.selectById(planId);
			Long elevatorId = currentPlan.getElevatorId();
			ElevatorVO elevatorVO = elevatorService.selectElevator(elevatorId);
			thing18 = thing18 + elevatorVO.getBuildingName() + "-" + elevatorVO.getElevatorAddress() + ";";
		}

		boolean confirm = Func.isNotEmpty(sendList);
		if (confirm) {
			List<Long> userIds = sendList.stream().map(MaintainPlanContentUser::getUserId).distinct().collect(Collectors.toList());
			for (Long userId : userIds) {
				SimpleUserVO assignUserInfo = userService.getSimpleUserInfo(userId);
				if (Func.isNotEmpty(assignUserInfo)) {
					if (Func.isNotEmpty(assignUserInfo.getOpenid())) {
						String skipUrl = "pages/home/index?footerKey=myTask&typeStatus=5";
						Map<String, String> templateDataMap = new HashMap<>(8);
						templateDataMap.put("first", "工单处理提醒");
						templateDataMap.put("thing10", "事务工单");
						templateDataMap.put("thing9", "您有事务工单发生变更");
						templateDataMap.put("thing18", thing18.length() > 16?thing18.substring(0, 15):thing18);
						templateDataMap.put("thing7", userName);
						templateDataMap.put("time8", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
						templateDataMap.put("remark", "请及时登录梯无忧小程序执行事务任务!");
						templateDataMap.put("params", skipUrl);
						weChatDialogueService.sendMsg(assignUserInfo.getOpenid(), TemplateMsgConstant.AFFAIR_MESSAGE, templateDataMap);
					}
					log.info("===========给事务工单执行人发送手机短信, 手机号码:[{}]===========", assignUserInfo.getUserName(), assignUserInfo.getPhone());
					Map<String, String> smsParam = new HashMap<>(2);
					smsParam.put("status", "待开始");
					SmsUtil.sendMessage(MaintenanceSmsConstant.AFFAIR_MESSAGE, BladeConstant.ADMIN_TENANT_ID, smsParam, assignUserInfo.getPhone());
				}
			}
		}
	}

	private void updateWorkMalfunctionLog(Long planId, List<MaintainPlanContentUser> list, Integer planStatus) {
		// 创建任务追踪对象，获取所需数据，插入操作
		String typeName;
		if (planStatus.equals(TaskStatusEnum.SW_ALLOCATION.getStatus())) {
			typeName = "指派人员";
		} else {
			typeName = "转派人员";
		}
		Long executorId = AuthUtil.getUserId();
		String executor = userService.getById(executorId).getRealName();
		if (Func.isNotEmpty(list)) {
			List<Long> tempIds = list
				.stream()
				.map(MaintainPlanContentUser::getUserId)
				.distinct()
				.collect(Collectors.toList());

			List<Long> existList = this.list(new LambdaQueryWrapper<MaintainPlanContentUser>()
				.eq(MaintainPlanContentUser::getPlanId, planId)
				.eq(BaseEntity::getIsDeleted, 0)
			).stream()
				.map(MaintainPlanContentUser::getUserId)
				.collect(Collectors.toList());

			tempIds.addAll(existList);
			tempIds = tempIds.stream().distinct().collect(Collectors.toList());

			List<String> userNames = userService.listByIds(tempIds)
				.stream()
				.map(User::getRealName)
				.collect(Collectors.toList());
			MaintainMalfunctionLog maintainMalfunctionLog = new MaintainMalfunctionLog();
			maintainMalfunctionLog.setPlanId(planId);
			maintainMalfunctionLog.setPlanStatus(maintainPlanContentService.getById(planId).getStatus());
			maintainMalfunctionLog.setExecutor(executor);
			maintainMalfunctionLog.setUserNames(String.join("/", userNames));
			maintainMalfunctionLog.setTypeName(typeName + "[" + String.join("/", userNames) + "]");
			malfunctionLogService.save(maintainMalfunctionLog);
		} else {
			List<Long> existList = this.list(new LambdaQueryWrapper<MaintainPlanContentUser>()
				.eq(MaintainPlanContentUser::getPlanId, planId)
				.eq(BaseEntity::getIsDeleted, 0)
			).stream()
				.map(MaintainPlanContentUser::getUserId)
				.collect(Collectors.toList());
			List<String> userNames = userService.listByIds(existList)
				.stream()
				.map(User::getRealName)
				.collect(Collectors.toList());
			MaintainMalfunctionLog maintainMalfunctionLog = new MaintainMalfunctionLog();
			maintainMalfunctionLog.setPlanId(planId);
			maintainMalfunctionLog.setPlanStatus(maintainPlanContentService.getById(planId).getStatus());
			maintainMalfunctionLog.setExecutor(executor);
			maintainMalfunctionLog.setTypeName(typeName + "[" + String.join("/", userNames) + "]");
			maintainMalfunctionLog.setUserNames(String.join("/", userNames));
			malfunctionLogService.save(maintainMalfunctionLog);
		}

	}

	/**
	 * 公共方法抽取
	 *
	 * @param planId  当前工单id
	 * @param userIds 当前前端传入 userId
	 * @return 获取人员异同
	 */
	private Map<String, List<Long>> getDiff(Long planId, List<Long> userIds) {
		Map<String, List<Long>> result = new HashMap<>(2);
		// 抽取 公共 user id 列表:  当前工单与公共列表之差 -> 逻辑删 ; 入参与公共列表之差 -> 新增
		// 当前已有指派列表
		List<MaintainPlanContentUser> existList = this.list(new LambdaQueryWrapper<MaintainPlanContentUser>()
			.eq(MaintainPlanContentUser::getPlanId, planId)
			.eq(BaseEntity::getIsDeleted, 0)
		);
		// 求公共 id
		List<Long> existUserIds = existList
			.stream()
			.map(MaintainPlanContentUser::getUserId)
			.collect(Collectors.toList());
		List<Long> publicUserIds = new ArrayList<>();
		for (Long userId : userIds) {
			if (existUserIds.contains(userId)) {
				publicUserIds.add(userId);
			}
		}

		// 已有 id - 公共 id = 待删除的 id
		List<Long> preDeleteList = existUserIds
			.stream()
			.filter(f -> !publicUserIds.contains(f))
			.collect(Collectors.toList());

		// 入参 id - 公共 id = 待新增的 id
		List<Long> preAddList = userIds
			.stream()
			.filter(f -> !publicUserIds.contains(f))
			.collect(Collectors.toList());

		result.put("preDeleteList", preDeleteList);
		result.put("preAddList", preAddList);
		return result;
	}

	/**
	 * 公共方法抽取
	 * 循环赋值
	 */
	private MaintainPlanContentUser sendElement(Long userId, Long planId, Date planDate) {
		MaintainPlanContentUser maintainPlanContentUser = new MaintainPlanContentUser();
		maintainPlanContentUser.setPlanId(planId);
		maintainPlanContentUser.setUserId(userId);
		maintainPlanContentUser.setPlanDate(planDate);
		// 补充签到细节信息
		PlanUserSignDetailVo planUserSignDetailVo = new PlanUserSignDetailVo();
		planUserSignDetailVo.setUserName(userService.getById(userId).getRealName());
		planUserSignDetailVo.setPlanId(planId);
		maintainPlanContentUser.setSignInfo(JSON.toJSONString(planUserSignDetailVo));
		return maintainPlanContentUser;
	}

	@Override
	public List<MaintainPlanContentUser> listAllContentUsersByPlanId(Long currentPlanId) {
		return baseMapper.listAllContentUsersByPlanId(currentPlanId);
	}

	@Override
	public List<MaintainPlanContentUser> listAllByPlanIds(List<Long> planIds) {
		return baseMapper.listAllByPlanIds(planIds);
	}

}
